#include <s3e.h>

#include "lib/drivers/pvmdriver_mamalade_s3esound.h"
#include "lib/drivers/pvmdeviceinterface.h"
#include "lib/pvmerrors.h"
#include "lib/helpers/pvmoutstreamhelpers.h"
#include "lib/pvmstrings.h"
#include "lib/pvmdriver.h"
#include "lib/sys/common/pvmspinlock.h"

#include "IwDebug.h"
#include "IwUtil.h"

#include "lib/memory/pvmmemory.h"

using namespace std;

#define PVMDS3ESOUND_WAIT_FOR_SUSPEND		1000
#define PVMDS3ESOUND_DEFAULT_BUFFER_LENGTH	512
#define PVMDS3ESOUND_DEVICE_TYPE	EPVM_DTYPE_OUTPUT|EPVM_DTYPE_STREAM_WRITER

static const int TMP_SAMPLES_NUM = 44100*4;
static signed short TMP_SAMPLES_BUFFER_16BIT[TMP_SAMPLES_NUM];

class PVMDevices3eSound : public IPVMDeviceOutSimple
{
public:
	PVMDevices3eSound(PVMDrivers3eSound* driver);
	~PVMDevices3eSound();

	virtual pvm_uint32 GetDeviceType();
	virtual void DeviceClose();
	virtual pvm_bool DeviceOpened();

	virtual pvm_bool SetOption(const pvm_char* optionName, void* param, pvm_uint32 paramSize);
	virtual pvm_bool GetOption(const pvm_char* optionName, void* param, pvm_uint32* paramSize);

	virtual void Suspend();
	virtual pvm_bool IsSuspended();
	virtual void Resume();

	virtual void DeviceNotify();

	virtual void SetBufferLengthMs(pvm_uint32 bufferSize);
	virtual pvm_uint32 GetBufferRealLengthMs();

	virtual pvm_int32 GetCPUUsage();

	virtual pvm_uint32 GetChanelsNum();
	virtual pvm_uint32 GetBitsPerSample();
	virtual pvm_uint32 GetSamplesPerSecond();

	pvm_bool Init(pvm_uint32 samplesPerSec, pvm_uint32 bitsPerSample, pvm_uint32 chanelsNum, pvm_int32 deviceNum, void *additionalInfo, pvm_uint32 infoSize, IPVMDeviceOutSimpleWaveSource *waveSource, pvm_bool deviceSuspended);

	virtual IPVMDriverBase* GetDriver();
	void OnIdle();
protected:
	void DeInit();
	static int32 EndSampleCallback(void* sys, void* user);
	static int32 AudioCallback(void* sys, void* user);
	int renderAudio(s3eSoundGenAudioInfo* info);
protected:
	int playingChannelNum;

	volatile pvm_bool isDeviceInitialized;
	volatile pvm_int isPlayingStarted;
	
	volatile pvm_bool pauseRendering;
	volatile pvm_bool isRenderingPaused;

	pvm_uint32			currentSampleRate;
	pvm_uint32			currentbitsPerSample;
	pvm_uint32			currentbytesPerSample;
	pvm_uint32			currentChanelsNum;

	pvm_uint32 renderBufferSizeInBytes;
	pvm_uint32 renderBufferSizeInSamples;
	pvm_int16* renderBuffer;
	
	IPVMDeviceOutSimpleWaveSource*	waveSource;
	PVMDrivers3eSound*				driver;
	PVMSpinLock						commonVarsLock;
};

pvm_uint32 PVMDevices3eSound::GetChanelsNum()
{
	return currentChanelsNum;
}

pvm_uint32 PVMDevices3eSound::GetBitsPerSample()
{
	return currentbitsPerSample;
}

pvm_uint32 PVMDevices3eSound::GetSamplesPerSecond()
{
	return currentSampleRate;
}

pvm_int32 PVMDevices3eSound::GetCPUUsage()
{
	return -1;
}

PVMDevices3eSound::PVMDevices3eSound(PVMDrivers3eSound* driver)
{
	PVMSpinLockInit(commonVarsLock);

	playingChannelNum = -1;

	isDeviceInitialized = false;
	isPlayingStarted = 0;

	pauseRendering = false;
	isRenderingPaused = true;

	currentSampleRate = 0;
	currentbitsPerSample = 16;
	currentbytesPerSample = 2;
	currentChanelsNum = 0;

	renderBufferSizeInBytes = 0;
	renderBufferSizeInSamples = 0;
	renderBuffer = NULL;

	waveSource = NULL;
	this->driver = driver;
}

PVMDevices3eSound::~PVMDevices3eSound()
{
	DeInit();
}

IPVMDriverBase* PVMDevices3eSound::GetDriver()
{
	return driver;
}

pvm_bool PVMDevices3eSound::Init(pvm_uint32 samplesPerSec, pvm_uint32 bitsPerSample, pvm_uint32 chanelsNum, pvm_int32 /*deviceNum*/, void *additionalInfo, pvm_uint32 infoSize, IPVMDeviceOutSimpleWaveSource *waveSource, pvm_bool deviceSuspended)
{
	renderBufferSizeInSamples = TMP_SAMPLES_NUM;
	renderBufferSizeInBytes = renderBufferSizeInSamples * currentbytesPerSample;

	memset(TMP_SAMPLES_BUFFER_16BIT, 0, sizeof(TMP_SAMPLES_BUFFER_16BIT));

	isDeviceInitialized = false;

	currentChanelsNum = s3eSoundGetInt(S3E_SOUND_STEREO_ENABLED) ? 2 : 1;

	playingChannelNum = s3eSoundGetFreeChannel();
	s3eSoundChannelRegister(playingChannelNum, S3E_CHANNEL_END_SAMPLE, EndSampleCallback, this);
	
	s3eSoundChannelRegister(playingChannelNum, S3E_CHANNEL_GEN_AUDIO, AudioCallback, this);
	if(currentChanelsNum==2)
		s3eSoundChannelRegister(playingChannelNum, S3E_CHANNEL_GEN_AUDIO_STEREO, AudioCallback, this);
    

	currentSampleRate = s3eSoundGetInt(S3E_SOUND_OUTPUT_FREQ);

	this->waveSource = waveSource;
	isDeviceInitialized = true;

	waveSource->SetForceProcessingOnIdle(true);
	
	//DeviceNotify();

	return true;
}

void PVMDevices3eSound::DeInit()
{
	if(isDeviceInitialized)
	{
		if(playingChannelNum>=0 && isPlayingStarted > 0)
		{
			s3eSoundChannelStop(playingChannelNum);
		}

		if(playingChannelNum>=0)
		{
			if(currentChanelsNum==2)
				s3eSoundChannelUnRegister(playingChannelNum, S3E_CHANNEL_GEN_AUDIO_STEREO);
			s3eSoundChannelUnRegister(playingChannelNum, S3E_CHANNEL_GEN_AUDIO);
		}

		playingChannelNum = -1;

		isDeviceInitialized = false;

		pauseRendering = false;
		isRenderingPaused = true;

		currentSampleRate = 0;
		currentbitsPerSample = 16;
		currentbytesPerSample = 2;
		currentChanelsNum = 0;

		renderBufferSizeInBytes = 0;
		renderBufferSizeInSamples = 0;
		PVMMemoryFree(renderBuffer);
		renderBuffer = NULL;

		waveSource = NULL;
	}
}

pvm_uint32 PVMDevices3eSound::GetDeviceType()
{
	return PVMDS3ESOUND_DEVICE_TYPE;
}

void PVMDevices3eSound::DeviceClose()
{
	DeInit();
	delete this;
}

pvm_bool PVMDevices3eSound::DeviceOpened()
{
	return isDeviceInitialized;
}

pvm_bool PVMDevices3eSound::SetOption(const pvm_char* /*optionName*/, void* /*param*/, pvm_uint32 /*paramSize*/)
{
	return false;
}

pvm_bool PVMDevices3eSound::GetOption(const pvm_char* /*optionName*/, void* /*param*/, pvm_uint32* /*paramSize*/)
{
	return false;
}

void PVMDevices3eSound::Suspend()
{
	if(pauseRendering)
		return;
	
	pauseRendering = true;
}

pvm_bool PVMDevices3eSound::IsSuspended()
{
	return isRenderingPaused&&pauseRendering;
}

void PVMDevices3eSound::Resume()
{
	pauseRendering = false;
}

void PVMDevices3eSound::DeviceNotify()
{
	if(!isDeviceInitialized)
		return;

	if(waveSource->GetRenderBufferSizeInBytes()==0)
	{
		waveSource->ResizeRenderBuffer(renderBufferSizeInBytes);
	}

	if(renderBuffer==NULL)
	{
		renderBuffer = (pvm_int16*)PVMMemoryAlloc(renderBufferSizeInSamples * sizeof(pvm_int16));
	}
	
	PVMSpinLockAcquire(commonVarsLock);
	if(!pauseRendering&&isRenderingPaused)
	{
		isRenderingPaused = false;
	}

	if(!isRenderingPaused && (isPlayingStarted <= 0))
	{
		isPlayingStarted++;
		if(s3eSoundChannelPlay(playingChannelNum, TMP_SAMPLES_BUFFER_16BIT, TMP_SAMPLES_NUM, 0, 0)!=S3E_RESULT_SUCCESS)
			isPlayingStarted--;
	}

	PVMSpinLockRelease(commonVarsLock);
}

void PVMDevices3eSound::SetBufferLengthMs(pvm_uint32 bufferSize)
{
}

pvm_uint32 PVMDevices3eSound::GetBufferRealLengthMs()
{
	return 0;
}

int32 PVMDevices3eSound::EndSampleCallback(void* sys, void* user)
{
	PVMDevices3eSound* device = (PVMDevices3eSound*)user;

    s3eSoundEndSampleInfo* info = (s3eSoundEndSampleInfo*)sys;
	//if(info->m_RepsRemaining==0)

	info->m_NumSamples = 0;
	device->isPlayingStarted--;

    return 0;
}

int32 PVMDevices3eSound::AudioCallback(void* sys, void* user)
{
	PVMDevices3eSound* device = (PVMDevices3eSound*)user;
	if(device!=NULL)
		return device->renderAudio((s3eSoundGenAudioInfo*)sys);

	return 0;
}

int PVMDevices3eSound::renderAudio(s3eSoundGenAudioInfo* info)
{
	pvm_uint32 samplesRendered = 0;
	bool needSuspendPlayback = false;

	if(!isDeviceInitialized)
	{
		info->m_EndSample = 1;
		PVMSpinLockAcquire(commonVarsLock);
		isRenderingPaused = true;
		PVMSpinLockRelease(commonVarsLock);

		return 0;
	}

	PVMSpinLockAcquire(commonVarsLock);

	if(waveSource==NULL || waveSource->IsInitialized()==false)
	{
		needSuspendPlayback = true;
	}
	else if((pauseRendering&&!isRenderingPaused)||!waveSource->HasMoreData() || waveSource->GetRenderBufferSizeInBytes()==0 || renderBuffer==NULL && info->m_Mix)
	{
		needSuspendPlayback = true;
	}
	else if(info->m_NumSamples <=0)
	{
		PVMSpinLockRelease(commonVarsLock);
		return 0;
	}

	int newChanelsNum = info->m_Stereo ? 2 : 1;
	if(newChanelsNum!=currentChanelsNum)
	{
		needSuspendPlayback = true;
	}

	if(needSuspendPlayback)
	{
		
		info->m_EndSample = true;
		info->m_OrigRepeat = 1;
		if(!info->m_Mix)
			memset(info->m_Target, 0, info->m_NumSamples * 2);
		isRenderingPaused = true;
		PVMSpinLockRelease(commonVarsLock);

		return info->m_NumSamples;
	}

	PVMSpinLockRelease(commonVarsLock);

	pvm_uint samplesToFill = info->m_Stereo ? info->m_NumSamples * 2 : info->m_NumSamples;

	if(info->m_Mix)
	{
			pvm_int16* localRenderBufferPtr = info->m_Target;

			while(samplesRendered < samplesToFill)
			{
				pvm_uint32 samplesToRender = samplesToFill - samplesRendered;
				if(samplesToRender > renderBufferSizeInSamples)
					samplesToRender = renderBufferSizeInSamples;

				pvm_uint32 samplesCurrentlyRendered = waveSource->FillBuffer(renderBuffer, samplesToRender);
				if(samplesCurrentlyRendered==0)
					break;

				for(unsigned int i=0; i<samplesCurrentlyRendered; i++)
					localRenderBufferPtr[i] += renderBuffer[i];

				samplesRendered += samplesCurrentlyRendered;
				localRenderBufferPtr += samplesCurrentlyRendered;
			}
	}
	else
	{
		if(samplesToFill < renderBufferSizeInSamples)
		{
			samplesRendered = waveSource->FillBuffer(info->m_Target, samplesToFill);
		}
		else
		{
			pvm_int16* localRenderBufferPtr = info->m_Target;

			while(samplesRendered < samplesToFill)
			{
				pvm_uint32 samplesToRender = samplesToFill - samplesRendered;
				if(samplesToRender > renderBufferSizeInSamples)
					samplesToRender = renderBufferSizeInSamples;

				pvm_uint32 samplesCurrentlyRendered = waveSource->FillBuffer(localRenderBufferPtr, samplesToRender);
				if(samplesCurrentlyRendered==0)
					break;

				samplesRendered += samplesCurrentlyRendered;
				localRenderBufferPtr += samplesCurrentlyRendered;
			}
		}
	}

	if(info->m_Stereo)
		samplesRendered = samplesRendered >>1;

	return samplesRendered;
}

void PVMDevices3eSound::OnIdle()
{
}

////////////////////////////////////////////////////////////////////////////////
//PVMDrivers3eSound															  //
////////////////////////////////////////////////////////////////////////////////

PVMDrivers3eSound::PVMDrivers3eSound()
{
}

PVMDrivers3eSound::~PVMDrivers3eSound()
{
}

pvm_bool PVMDrivers3eSound::Init()
{

	return true;
}

pvm_bool PVMDrivers3eSound::IsInitialized()
{
	return true;
}

void PVMDrivers3eSound::DriverClose()
{
	delete this;
}

pvm_uint32 PVMDrivers3eSound::GetDriverType()
{
	return PVMDS3ESOUND_DEVICE_TYPE;
}

pvm_bool PVMDrivers3eSound::GetDriverOption(const pvm_char* /*optionName*/, void* /*optionVal*/, pvm_uint32* /*optionSize*/)
{
	return false;
}

pvm_bool PVMDrivers3eSound::SetDriverOption(const pvm_char* /*optionName*/, void* /*optionVal*/, pvm_uint32 /*optionSize*/)
{
	return false;
}


pvm_uint32 PVMDrivers3eSound::DevicesNum()
{
	return 1;
}

pvm_int32 PVMDrivers3eSound::DeviceName(pvm_int32 /*nDevice*/, pvm_char* buffer, pvm_uint32 maxLength)
{
	if(buffer==0||maxLength==0)
		return 11;
	if(maxLength<pvm_strlen(_PVM_STR("s3eSound output device")) + 1)
		return 0;
	pvm_strcpy(buffer, _PVM_STR("s3eSound output device"));

	return pvm_strlen(buffer);
}

pvm_bool PVMDrivers3eSound::DeviceEnumModes(pvm_int32 /*nDeivce*/, TFormatEnumCallbackProc enumProc, void *param)
{
	if(!enumProc)
		return false;

	int chanelsNum = s3eSoundGetInt(S3E_SOUND_STEREO_ENABLED) ? 2 : 1;
	int sampleRate = s3eSoundGetInt(S3E_SOUND_OUTPUT_FREQ);

	enumProc(chanelsNum, 16, sampleRate, param);

	return true;
}

IPVMDeviceOutSimple* PVMDrivers3eSound::DeviceOpen(pvm_uint32 samplesPerSec, pvm_uint32 bitsPerSample, pvm_uint32 chanelsNum, pvm_int32 deviceNum, void *additionalInfo, pvm_uint32 infoSize, IPVMDeviceOutSimpleWaveSource *waveSource, pvm_bool deviceSuspended)
{
	if(!IsInitialized())
		return NULL;

	if(waveSource==NULL)
		return NULL;

	PVMDevices3eSound* device = new PVMDevices3eSound(this);
	if(!device)
		return NULL;
	if(!device->Init(samplesPerSec, bitsPerSample, chanelsNum, deviceNum, additionalInfo, infoSize, waveSource, deviceSuspended))
	{
		device->DeviceClose();
		return NULL;
	}

	devices.push_back(device);

	return device;
}

void PVMDrivers3eSound::CloseDevice(IPVMDeviceBase *device)
{
	if(device!=NULL)
	{
		PVMDevices3eSound* s3eDevice = (PVMDevices3eSound*)device;
		devices.remove(s3eDevice);
		device->DeviceClose();
	}
}

void PVMDrivers3eSound::OnIdle()
{
	for(std::list<PVMDevices3eSound*>::iterator iter = devices.begin(); iter != devices.end(); ++iter)
	{
		(*iter)->OnIdle();
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

IPVMDriverOut* PVMDriverCreates3eSound()
{
	PVMDrivers3eSound* outDrv = new PVMDrivers3eSound();
	if(!outDrv)
		return NULL;
	PVMDriverSimple* simpledrv = new PVMDriverSimple(outDrv);
	if(!simpledrv)
	{
		outDrv->DriverClose();
	}

	return simpledrv;
}

static pvm_int32 DriverNameShort(pvm_char* buffer, pvm_uint32 maxLength)
{
	return PVMCopyDriverName(_PVM_STR("s3eSound output driver"), buffer, maxLength);
}

static pvm_int32 DriverNameFull(pvm_char* buffer, pvm_uint32 maxLength)
{
	return PVMCopyDriverName(_PVM_STR("s3eSound output driver"), buffer, maxLength);
}

static pvm_uint32 DriverTypeAndFlags()
{
	return PVMDS3ESOUND_DEVICE_TYPE;
}

static IPVMDriverBase* DriverLoad()
{
	return PVMDriverCreates3eSound();
}

IPVMDriverDesc	s3eSoundDriverDesc;

IPVMDriverDesc* PVMGetDriverDescs3eSound()
{
	s3eSoundDriverDesc.DriverNameShort = DriverNameShort;
	s3eSoundDriverDesc.DriverNameFull = DriverNameFull;
	s3eSoundDriverDesc.DriverTypeAndFlags = DriverTypeAndFlags;
	s3eSoundDriverDesc.DriverLoad = DriverLoad;
	return &s3eSoundDriverDesc;
}

#undef	PVMDS3ESOUND_WAIT_FOR_SUSPEND
#undef	PVMDS3ESOUND_DEFAULT_BUFFER_LENGTH
#undef	PVMDS3ESOUND_DEVICE_TYPE
